using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Configuration;

namespace NBear.Common
{
    /// <summary>
    /// Base Entity Object
    /// </summary>
    /// <typeparam name="IEntityType"></typeparam>
    [Serializable]
    public abstract class Entity<IEntityType>
        where IEntityType : IEntity
    {
        #region Protected Members

        protected static KeyValueCollection keyValuesTemplate = null;
        protected KeyValueCollection keyValues;
        protected KeyValueCollection originalKeyValues;

        protected KeyValueCollection GetOriginalKeyValues()
        {
            return originalKeyValues ?? keyValues;
        }

        protected void CheckOriginalKeyValues()
        {
            if (originalKeyValues == null)
            {
                originalKeyValues = keyValues.Clone();
            }
        }

        protected void UpdateOriginalKeyValues()
        {
            originalKeyValues = keyValues.Clone();
        }

        #endregion

        #region Internal Members

        internal static KeyValueCollection KeyValuesTemplate
        {
            get
            {
                return keyValuesTemplate;
            }
        }

        #endregion

        #region Private Members

        private static string tableName;
        private static string additionalWhere;
        private static string additionalInsert;
        private static bool isView;
        private static bool isContract;
        private static bool isCascadeUpdate;
        private static string updateTableName;
        private static Dictionary<string, string> propertyToColumn;
        private static List<string> primaryKeyColumnNames;
        private static List<string> readonlyColumnNames;
        private static List<string> updatableColumnNames;
        private static List<string> insertColumnNames;
        private static string storedProcedureName;
        private static bool storedProcedureFillByIndex;

        private static void InitKeyValuesTemplate()
        {
            CheckTableName();

            List<PropertyInfo> list = new List<PropertyInfo>();
            foreach (PropertyInfo pi in Util.DeepGetProperties(typeof(IEntityType)))
            {
                object[] attrs = pi.GetCustomAttributes(typeof(CustomPropertyAttribute), false);
                if (attrs == null || attrs.Length == 0)
                {
                    list.Add(pi);
                }
            }

            PropertyInfo[] pis = list.ToArray();

            CheckColumnNames(pis);

            CheckPrimaryKeyNames(pis);

            CheckReadOnlyColumnNames(pis);

            keyValuesTemplate = new KeyValueCollection(pis, propertyToColumn);
        }

        private static void CheckTableName()
        {
            //get table name
            TableAttribute t = (GetEntityConfiguration() == null ? null : GetEntityConfiguration().Table);

            object[] attrs = (t == null ? typeof(IEntityType).GetCustomAttributes(typeof(TableAttribute), false) : new object[] { t });

            if (attrs != null && attrs.Length > 0)
            {
                TableAttribute ta = ((TableAttribute)attrs[0]);
                if (string.IsNullOrEmpty(ta.Name))
                {
                    tableName = typeof(IEntityType).Name;
                }
                else
                {
                    tableName = ta.Name;
                }
                isView = ta.IsView;
                isContract = ta.IsContract;
                isCascadeUpdate = ta.IsCascadeUpdate;
                updateTableName = ta.UpdateTableName;
                additionalWhere = ta.AdditionalWhere;
                additionalInsert = ta.AdditionalInsert;
            }
            else
            {
                tableName = typeof(IEntityType).Name;
            }

            //get store procedure name
            StoreProcedureAttribute spa = (GetEntityConfiguration() == null ? null : GetEntityConfiguration().StoredProcedure);

            if (spa == null)
            {
                attrs = typeof(IEntityType).GetCustomAttributes(typeof(StoreProcedureAttribute), false);
                if (attrs != null && attrs.Length > 0)
                {
                    spa = (StoreProcedureAttribute)attrs[0];
                    if (string.IsNullOrEmpty(spa.Name))
                    {
                        spa.Name = typeof(IEntityType).Name;
                    }
                }
            }

            if (spa != null)
            {
                storedProcedureName = spa.Name;
                storedProcedureFillByIndex = spa.FillByIndex;
            }
            else
            {
                storedProcedureName = typeof(IEntityType).Name;
            }
        }

        private static void CheckPrimaryKeyNames(PropertyInfo[] pis)
        {
            primaryKeyColumnNames = new List<string>();
            foreach (PropertyInfo pi in pis)
            {
                PrimaryKeyAttribute pa = (GetEntityConfiguration() == null ? null : GetEntityConfiguration().GetPrimaryKey(pi.Name));
                object[] attrs = (pa == null ? pi.GetCustomAttributes(typeof(PrimaryKeyAttribute), false) : new object[] { pa });
                if (attrs != null && attrs.Length > 0 && (!primaryKeyColumnNames.Contains(pi.Name)))
                {
                    primaryKeyColumnNames.Add(GetPropertyMappedColumn(pi.Name));
                }
            }
        }

        private static void CheckReadOnlyColumnNames(PropertyInfo[] pis)
        {
            readonlyColumnNames = new List<string>();
            foreach (PropertyInfo pi in pis)
            {
                if ((!pi.CanWrite) && (!readonlyColumnNames.Contains(pi.Name)))
                {
                    readonlyColumnNames.Add(GetPropertyMappedColumn(pi.Name));
                }
            }
        }

        private static void CheckColumnNames(PropertyInfo[] pis)
        {
            propertyToColumn = new Dictionary<string, string>();

            foreach (PropertyInfo pi in pis)
            {
                //check column names
                ColumnNameAttribute ca = (GetEntityConfiguration() == null ? null : GetEntityConfiguration().GetColumnName(pi.Name));
                object[] attrs = (ca == null ? pi.GetCustomAttributes(typeof(ColumnNameAttribute), false) : new object[] { ca });
                if (attrs != null && attrs.Length > 0)
                {
                    string columnName = ((ColumnNameAttribute)attrs[0]).Name;

                    if (columnName != pi.Name)
                    {
                        propertyToColumn.Add(pi.Name, columnName);
                    }
                }
            }
        }

        public static EntityConfiguration GetEntityConfiguration()
        {
            return GetEntityConfiguration(typeof(IEntityType).FullName);
        }

        public static EntityConfiguration GetEntityConfiguration(string entityFullName)
        {
            object section = ConfigurationManager.GetSection("EntityConfiguration");

            if (section == null)
            {
                return null;
            }

            if (typeof(System.Xml.XmlNodeList).IsAssignableFrom(section.GetType()))
            {
                section = new EntityConfigurationSection().Create(null, null, ((System.Xml.XmlNodeList)section)[0]);
            }

            if (section.GetType() == typeof(EntityConfigurationCollection))
            {
                return ((EntityConfigurationCollection)section)[entityFullName];
            }

            return null;
        }

        #endregion

        #region Public Memebrs

        public static string GetTableName()
        {
            return tableName;
        }

        public static string GetTableAdditionalWhere()
        {
            return additionalWhere;
        }

        public static string GetTableAdditionalInsert()
        {
            return additionalInsert;
        }

        public static bool IsView()
        {
            return isView;
        }

        public static bool IsContract()
        {
            return isContract;
        }

        public static bool IsCascadeUpdate()
        {
            return isCascadeUpdate;
        }

        public static string GetUpdateTableName()
        {
            return updateTableName;
        }

        public static string[] GetColumnNames(params string[] exceptColumns)
        {
            return keyValuesTemplate.GetKeys(exceptColumns);
        }

        public static string[] GetPrimaryKeyColumnNames()
        {
            return primaryKeyColumnNames.ToArray();
        }

        public static string[] GetReadOnlyColumnNames()
        {
            return readonlyColumnNames.ToArray();
        }

        public static string[] GetUpdatableColumnNames(params string[] exceptColumns)
        {
            updatableColumnNames = new List<string>();
            
            foreach (string column in GetColumnNames(exceptColumns))
            {
                if ((!primaryKeyColumnNames.Contains(column)) && (!readonlyColumnNames.Contains(column)))
                {
                    updatableColumnNames.Add(column);
                }
            }

            if (isCascadeUpdate)
            {
                //remove inherited non-contract updatable column names
                Type[] interfaceTypes = typeof(IEntityType).GetInterfaces();

                foreach (Type interfaceType in interfaceTypes)
                {
                    if (typeof(IEntity).IsAssignableFrom(interfaceType) && interfaceType != typeof(IEntity) && (!IsContractEntity(interfaceType)))
                    {
                        string[] interfaceUpdatableColumnNames = (string[])(typeof(Entity<>).MakeGenericType(interfaceType).GetMethod("GetUpdatableColumnNames", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Invoke(null, new object[] { new string[0] }));

                        if (interfaceUpdatableColumnNames != null && interfaceUpdatableColumnNames.Length > 0)
                        {
                            foreach (string item in interfaceUpdatableColumnNames)
                            {
                                if (updatableColumnNames.Contains(item) && (!primaryKeyColumnNames.Contains(item)))
                                {
                                    updatableColumnNames.Remove(item);
                                }
                            }
                        }
                    }
                }
            }

            return updatableColumnNames.ToArray();
        }

        public static string[] GetInsertColumnNames(params string[] exceptColumns)
        {
            insertColumnNames = new List<string>();
            foreach (string column in GetColumnNames(exceptColumns))
            {
                if (!readonlyColumnNames.Contains(column))
                {
                    insertColumnNames.Add(column);
                }
            }

            if (isCascadeUpdate && (!string.IsNullOrEmpty(updateTableName)))
            {
                //remove inherited non-contract insert column names
                Type[] interfaceTypes = typeof(IEntityType).GetInterfaces();

                foreach (Type interfaceType in interfaceTypes)
                {
                    if (typeof(IEntity).IsAssignableFrom(interfaceType) && interfaceType != typeof(IEntity) && (!IsContractEntity(interfaceType)))
                    {
                        string[] interfaceInsertColumnNames = (string[])(typeof(Entity<>).MakeGenericType(interfaceType).GetMethod("GetInsertColumnNames", BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static).Invoke(null, new object[] { new string[0] }));

                        if (interfaceInsertColumnNames != null && interfaceInsertColumnNames.Length > 0)
                        {
                            foreach (string item in interfaceInsertColumnNames)
                            {
                                if (insertColumnNames.Contains(item) && (!primaryKeyColumnNames.Contains(item)))
                                {
                                    insertColumnNames.Remove(item);
                                }
                            }
                        }
                    }
                }
            }

            return insertColumnNames.ToArray();
        }

        private static bool IsContractEntity(Type interfaceType)
        {
            TableAttribute ta = (GetEntityConfiguration(interfaceType.FullName) == null ? null : GetEntityConfiguration(interfaceType.FullName).Table);
            if (ta == null)
            {
                object[] attrs = interfaceType.GetCustomAttributes(typeof(TableAttribute), false);
                if (attrs != null && attrs.Length > 0)
                {
                    ta = (TableAttribute)attrs[0];
                }
            }

            if (ta != null)
            {
                return ta.IsContract;
            }

            return false;
        }

        public static string GetStoreProcedureName()
        {
            return storedProcedureName;
        }

        public static bool GetStoreProcedureFillByIndex()
        {
            return storedProcedureFillByIndex;
        }

        public static string GetPropertyMappedColumn(string propertyName)
        {
            if (propertyToColumn.ContainsKey(propertyName))
            {
                return propertyToColumn[propertyName];
            }
            else
            {
                return propertyName;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the <see cref="T:Entity&lt;IEntityType&gt;"/> class.
        /// </summary>
        static Entity()
        {
            if (keyValuesTemplate == null)
            {
                InitKeyValuesTemplate();
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Entity&lt;IEntityType&gt;"/> class.
        /// </summary>
        public Entity()
        {
            keyValues = keyValuesTemplate.Clone();
        }

        #endregion
    }
}

